/**
 * @file
 * Application layered TCP/TLS connection API (to be used from TCPIP thread)
 *
 * This file provides a TLS layer using openHiTLS (Chinese national cryptography protocol library)
 */

#include "lwip/opt.h"


#if defined(LWIP_ALTCP) && defined(LWIP_ALTCP_TLS_HITLS)
#include <string.h>
#include "securec.h"

/* lwip header files */
#include "lwip/altcp.h"
#include "lwip/altcp_tcp.h"
#include "lwip/altcp_tls.h"
#include "lwip/priv/altcp_priv.h"
#include "lwip/mem.h"
#include "lwip/debug.h"
#include "lwip/pbuf.h"

/* HiTLS related header files */
#include "bsl_uio.h"
#include "bsl_errno.h"
#include "hitls.h"
#include "hitls_pki_cert.h"
#include "hitls_pki_crl.h"
#include "hitls_pki_x509.h"
#include "hitls_config.h"
#include "hitls_error.h"
#include "hitls_cert.h"
#include "hitls_error.h"

#include "altcp_tls_hitls_opts.h"
#include "altcp_tls_hitls_structs.h"

#define ALTCP_HITLS_DEBUG_SERIOUS      (ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_SERIOUS)

static const struct altcp_functions altcp_hitls_functions;

#ifdef LWIP_ALTCP_HITLS_PROVIDER
static CRYPT_EAL_LibCtx *g_libCtx = NULL;
static const char *g_attr = NULL;

void altcp_hitls_init(void *libCtx, const char *attr)
{
    g_libCtx = (CRYPT_EAL_LibCtx *)libCtx;

    char *attr_copy = (char *)mem_calloc(strlen(attr) + 1);
    if (attr_copy == NULL) {
        return;
    }
    /* Copy the attribute string */
    if (memcpy_s(attr_copy, strlen(attr) + 1, attr, strlen(attr) + 1) != 0) {
        mem_free(attr_copy);
        return;
    }
    if (g_attr != NULL) {
        mem_free((void *)g_attr);
    }
    g_attr = attr_copy;
}

void altcp_hitls_deinit(void)
{
    g_libCtx = NULL;
    if (g_attr != NULL) {
        mem_free(g_attr);
        g_attr = NULL;
    }
}
#endif

/**
 * Configure HiTLS authentication mode
 */
static void altcp_hitls_configure_auth_mode(HITLS_Config *config, uint8_t isAuth)
{
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
    if (isAuth > 0) {
        /* When auth macro is enabled and isAuth>0, set to required */
        (void)HITLS_CFG_SetClientVerifySupport(config, true);
        (void)HITLS_CFG_SetNoClientCertSupport(config, false);
        (void)HITLS_CFG_SetVerifyNoneSupport(config, false);
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Client verification set to REQUIRED\n"));
    } else {
        /* When auth macro is enabled and isAuth<=0, set to none */
        (void)HITLS_CFG_SetClientVerifySupport(config, false);
        (void)HITLS_CFG_SetNoClientCertSupport(config, true);
        (void)HITLS_CFG_SetVerifyNoneSupport(config, true);
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Client verification set to NONE\n"));
    }
#else
    /* When auth macro is not enabled, set to optional (default value) */
    (void)HITLS_CFG_SetClientVerifySupport(config, false);
    (void)HITLS_CFG_SetNoClientCertSupport(config, true);
    (void)HITLS_CFG_SetVerifyNoneSupport(config, false);
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Client verification set to OPTIONAL\n"));
#endif
}

/**
 * Parse PEM format CA certificate bundle and add to certificate store
 * @param config HiTLS configuration object
 * @param ca PEM format CA certificate data (may contain multiple certificates)
 * @param ca_len CA certificate data length
 * @return HITLS_SUCCESS if successful, error code otherwise
 */
static int32_t add_ca_certs_to_config(HITLS_Config *config, const uint8_t *ca, size_t ca_len)
{
    BSL_Buffer caBuf = {.data = (uint8_t *)(uintptr_t)ca, .dataLen = ca_len};
    HITLS_X509_List *caCerts = NULL;
    uint32_t certCount = 0;

#ifdef LWIP_ALTCP_HITLS_PROVIDER
    int32_t hiRet = HITLS_X509_ProviderCertParseBundleBuff(g_libCtx, g_attr, "PEM", &caBuf, &caCerts);
#else
    int32_t hiRet = HITLS_X509_CertParseBundleBuff(BSL_FORMAT_PEM, &caBuf, &caCerts);
#endif
    if (hiRet != HITLS_SUCCESS) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("CA certificate bundle parse failed: %d\n", hiRet));
        return hiRet;
    }

    /* Add each CA certificate to certificate store */
    HITLS_X509_Cert *tmp = BSL_LIST_GET_FIRST(caCerts);
    int ref;
    while (tmp != NULL) {
        hiRet = HITLS_CFG_AddCertToStore(config, tmp, TLS_CERT_STORE_TYPE_DEFAULT, true);
        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Add CA certificate to store failed: %d\n", hiRet));
            BSL_LIST_FREE(caCerts, (BSL_LIST_PFUNC_FREE)HITLS_X509_CertFree);
            return hiRet;
        }
        hiRet = HITLS_X509_CertCtrl(tmp, HITLS_X509_REF_UP, &ref, sizeof(int));
        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Add CA certificate to store failed: %d\n", hiRet));
            BSL_LIST_FREE(caCerts, (BSL_LIST_PFUNC_FREE)HITLS_X509_CertFree);
            return hiRet;
        }
        certCount++;
        tmp = BSL_LIST_GET_NEXT(caCerts);
    }

    /* Release certificate array (note: certificate object itself has been transferred to certificate store, no need to release) */
    BSL_LIST_FREE(caCerts, (BSL_LIST_PFUNC_FREE)HITLS_X509_CertFree);
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("All %d CA certificates added successfully\n", certCount));
    return HITLS_SUCCESS;
}

/**
 * UIO read callback function
 * Function: read data from lwip's pbuf for HiTLS use
 */
static int32_t altcp_hitls_uio_read(BSL_UIO *uio, void *buf, uint32_t len, uint32_t *readLen)
{
    altcp_hitls_uio_ctx_t *uio_ctx;
    altcp_hitls_state_t *state;
    struct pbuf *p;
    uint32_t copy_len;
    err_t err;

    if (uio == NULL || buf == NULL || readLen == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio readparameters\n"));
        return BSL_UIO_FAIL;
    }

    /* Get UIO context */
    uio_ctx = (altcp_hitls_uio_ctx_t *)BSL_UIO_GetUserData(uio);
    if (uio_ctx == NULL || uio_ctx->state == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio context\n"));
        return BSL_UIO_FAIL;
    }

    state = uio_ctx->state;
    p = state->rx;
    *readLen = 0;

    /* Check if there is data to read */
    if (p == NULL || (p->len == 0 && p->next == NULL)) {
        if (p != NULL) {
            pbuf_free(p);
            state->rx = NULL;
        }

        /* Check connection close status */
        if ((state->flags & (ALTCP_HITLS_FLAGS_RX_CLOSE_QUEUED | ALTCP_HITLS_FLAGS_RX_CLOSED)) ==
            ALTCP_HITLS_FLAGS_RX_CLOSE_QUEUED) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Connection closing while reading\n"));
            return BSL_UIO_IO_EOF; /* Connection is closing */
        }

        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("No data available, waiting\n"));
        return BSL_SUCCESS; /* Need to wait for more data */
    }

    /* Copy data */
    copy_len = LWIP_MIN(len, p->len);
    *readLen = pbuf_copy_partial(p, buf, copy_len, 0);

    /* Update pbuf */
    err = pbuf_remove_header(p, *readLen);
    LWIP_ASSERT("pbuf_remove_header failed", err == ERR_OK);

    if (p->len == 0) {
        state->rx = pbuf_dechain(p);
        pbuf_free(p);
    }

    /* Update statistics */
    state->uio_bytes_read += *readLen;

    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Uio read %d bytes successfully\n", *readLen));
    return BSL_SUCCESS;
}

/**
 * UIO write callback function
 * Function: write HiTLS encrypted data to lwip's TCP connection
 */
static int32_t altcp_hitls_uio_write(BSL_UIO *uio, const void *buf, uint32_t len, uint32_t *writeLen)
{
    altcp_hitls_uio_ctx_t *uio_ctx;
    altcp_hitls_state_t *state;
    struct altcp_pcb *conn;
    const uint8_t *dataptr = (const uint8_t *)buf;
    uint32_t size_left = len;
    uint32_t written = 0;
    uint8_t apiflags = TCP_WRITE_FLAG_COPY;

    if (uio == NULL || buf == NULL || writeLen == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio write parameters\n"));
        return BSL_UIO_FAIL;
    }

    uio_ctx = (altcp_hitls_uio_ctx_t *)BSL_UIO_GetUserData(uio);
    if (uio_ctx == NULL || uio_ctx->conn == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio context or connection\n"));
        return BSL_UIO_FAIL;
    }

    conn = uio_ctx->conn;
    state = uio_ctx->state;
    *writeLen = 0;

    if (conn->inner_conn == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Inner connection is NULL\n"));
        return BSL_UIO_IO_EXCEPTION;
    }

    /* Write data in chunks */
    while (size_left > 0) {
        uint16_t write_len = (uint16_t)LWIP_MIN(size_left, 0xFFFF);
        err_t err = altcp_write(conn->inner_conn, dataptr + written, write_len, apiflags);

        if (err == ERR_OK) {
            written += write_len;
            size_left -= write_len;
            /* Update HiTLS specific overhead statistics */
            state->overhead_bytes_adjust += write_len;
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Uio wrote %d bytes (total: %d)\n", write_len, written));
        } else if (err == ERR_MEM) {
            if (written > 0) {
                *writeLen = written;
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Partial write completed, %d bytes total\n",
                    written));
                return BSL_SUCCESS;
            }
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Buffer full, waiting for space\n"));
            return BSL_UIO_IO_BUSY; /* Wait for buffer space */
        } else {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_write failed: %d\n", err));
            return BSL_UIO_IO_EXCEPTION;
        }
    }

    *writeLen = written;
    return BSL_SUCCESS;
}

/**
 * UIO control callback function. Handle various control commands used by HITLS-TLS.
 */
static int32_t altcp_hitls_uio_ctrl(BSL_UIO *uio, int32_t cmd, int32_t larg, void *parg)
{
    altcp_hitls_uio_ctx_t *uio_ctx;

    if (uio == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio parameter\n"));
        return BSL_UIO_FAIL;
    }

    uio_ctx = (altcp_hitls_uio_ctx_t *)BSL_UIO_GetUserData(uio);
    if (!uio_ctx) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid uio context\n"));
        return BSL_UIO_FAIL;
    }

    switch (cmd) {
        case BSL_UIO_FLUSH:
            /* Trigger underlying TCP send */
            if (uio_ctx->conn && uio_ctx->conn->inner_conn) {
                altcp_output(uio_ctx->conn->inner_conn);
            } else {
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Flush failed, no connection\n"));
            }
            return BSL_SUCCESS;
        default:
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Unsupported uio ctrl cmd %d\n", cmd));
            return BSL_UIO_FAIL;
    }
}

/**
 * Create and configure UIO object
 */
static BSL_UIO *altcp_hitls_create_uio(struct altcp_pcb *conn, altcp_hitls_state_t *state)
{
    BSL_UIO *uio;
    altcp_hitls_uio_ctx_t *uio_ctx;

    /* Create UIO method */
    BSL_UIO_Method *method = BSL_UIO_NewMethod();
    if (!method) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("BSL_UIO_NewMethod failed\n"));
        return NULL;
    }

    /* Set method type and callback functions */
    (void)BSL_UIO_SetMethodType(method, BSL_UIO_TCP);
    (void)BSL_UIO_SetMethod(method, BSL_UIO_READ_CB, altcp_hitls_uio_read);
    (void)BSL_UIO_SetMethod(method, BSL_UIO_WRITE_CB, altcp_hitls_uio_write);
    (void)BSL_UIO_SetMethod(method, BSL_UIO_CTRL_CB, altcp_hitls_uio_ctrl);

    /* Create UIO object */
    uio = BSL_UIO_New(method);
    BSL_UIO_FreeMethod(method); /* Method has been copied, can be released */

    if (!uio) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("BSL_UIO_New failed\n"));
        return NULL;
    }
    BSL_UIO_SetInit(uio, true); /* Set UIO initialization state to true */

    /* Create and set UIO context */
    uio_ctx = (altcp_hitls_uio_ctx_t *)mem_malloc(sizeof(altcp_hitls_uio_ctx_t));
    if (uio_ctx == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to allocate UIO context\n"));
        BSL_UIO_Free(uio);
        return NULL;
    }

    uio_ctx->conn = conn;
    uio_ctx->state = state;

    /* Associate context with UIO - using HiTLS user data mechanism */
    (void)BSL_UIO_SetUserData(uio, uio_ctx);

    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("UIO created and configured successfully\n"));
    return uio;
}

/**
 * Pass received data to upper layer application
 */
static err_t altcp_hitls_pass_rx_data(struct altcp_pcb *conn, altcp_hitls_state_t *state)
{
    err_t err;
    struct pbuf *buf;
    LWIP_ASSERT("conn != NULL", conn != NULL);
    LWIP_ASSERT("state != NULL", state != NULL);
    if (state->rx_app != NULL) {
        buf = state->rx_app;
        state->rx_app = NULL;

        /* Call upper layer receive callback */
        if (conn->recv != NULL) {
            uint16_t tot_len = buf->tot_len;
            state->rx_passed_unrecved += tot_len;
            state->flags |= ALTCP_HITLS_FLAGS_UPPER_CALLED;
            err = conn->recv(conn->arg, conn, buf, ERR_OK);
            // state->flags &= ~ALTCP_HITLS_FLAGS_UPPER_CALLED;
            if (err != ERR_OK) {
                if (err == ERR_ABRT) {
                    return ERR_ABRT;
                }
                /* not received, leave the pbuf(s) queued */
                LWIP_ASSERT("state == conn->state", state == conn->state);
                state->rx_app = buf;
                state->rx_passed_unrecved -= tot_len;
                LWIP_ASSERT("state->rx_passed_unrecved >= 0", state->rx_passed_unrecved >= 0);
                if (state->rx_passed_unrecved < 0) {
                    state->rx_passed_unrecved = 0;
                }
                return err;
            }
        } else {
            pbuf_free(buf);
        }
    } else if ((state->flags & (ALTCP_HITLS_FLAGS_RX_CLOSE_QUEUED | ALTCP_HITLS_FLAGS_RX_CLOSED)) ==
               ALTCP_HITLS_FLAGS_RX_CLOSE_QUEUED) {
        /* Connection close queued, notify upper layer */
        state->flags |= ALTCP_HITLS_FLAGS_RX_CLOSED;
        if (conn->recv) {
            return conn->recv(conn->arg, conn, NULL, ERR_OK);
        }
    }

    /* Check if application has closed the connection */
    if (conn->state != state) {
        /* return error code to ensure altcp_hitls_lower_recv_process() exits the loop */
        return ERR_ARG;
    }
    return ERR_OK;
}

/**
 * Helper function that processes rx application data stored in rx pbuf chain
 */
static err_t altcp_hitls_handle_rx_appldata(struct altcp_pcb *conn, altcp_hitls_state_t *state)
{
    int32_t hiRet;
    LWIP_ASSERT("state != NULL", state != NULL);
    
    if (!(state->flags & ALTCP_HITLS_FLAGS_HANDSHAKE_DONE)) {
        /* handshake not done yet */
        return ERR_VAL;
    }
    
    do {
        /* Allocate a full-sized unchained PBUF_POOL: this is for RX! */
        struct pbuf *buf = pbuf_alloc(PBUF_RAW, PBUF_POOL_BUFSIZE, PBUF_POOL);
        if (buf == NULL) {
            /* We're short on pbufs, try again later from 'poll' or 'recv' callbacks.
               @todo: close on excessive allocation failures or leave this up to upper conn? */
            return ERR_OK;
        }

        /* Read decrypted application data using HiTLS API */
        uint32_t readLen = 0;
        hiRet = HITLS_Read(state->hitls_ctx, (uint8_t *)buf->payload, PBUF_POOL_BUFSIZE, &readLen);
        if (readLen > 0) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Decrypted %d bytes\n", readLen));
            LWIP_ASSERT("bogus receive length", readLen <= PBUF_POOL_BUFSIZE);

            /* Trim pool pbuf to actually decoded length */
            pbuf_realloc(buf, (u16_t)readLen);

            /* Add to application data chain */
            if (state->rx_app == NULL) {
                state->rx_app = buf;
            } else {
                pbuf_cat(state->rx_app, buf);
            }
            state->uio_bytes_appl += readLen;

            /* When HiTLS indicates no more data available in current record,
             * calculate overhead and acknowledge encrypted bytes to TCP layer */
            if (hiRet != HITLS_SUCCESS || hiRet == HITLS_REC_NORMAL_RECV_BUF_EMPTY) {
                if (state->uio_bytes_read > 0) {
                    int overhead_bytes;
                    LWIP_ASSERT("bogus byte counts", state->uio_bytes_read >= state->uio_bytes_appl);
                    overhead_bytes = state->uio_bytes_read - state->uio_bytes_appl;
                    /* Acknowledge only the TLS overhead bytes to TCP layer */
                    if (overhead_bytes > 0) {
                        altcp_recved(conn->inner_conn, overhead_bytes);
                    }
                    state->uio_bytes_read = 0;
                    state->uio_bytes_appl = 0;
                }
            }
        } else { // hiRet != HITLS_SUCCESS or readLen == 0
            /* No data read, free the pbuf */
            pbuf_free(buf);
            buf = NULL;
        }

        /* Pass decrypted data to upper layer */
        err_t err = altcp_hitls_pass_rx_data(conn, state);
        if (err != ERR_OK) {
            if (err == ERR_ABRT) {
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Connection aborted by upper layer\n"));
                return ERR_ABRT; /* Connection aborted */
            }
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Pass_rx_data returned error: %d\n", err));
            return ERR_OK; /* Ignore other errors, retry later */
        }
    } while (hiRet == HITLS_SUCCESS);

    /* Handle HiTLS read errors */
    if (hiRet != HITLS_REC_NORMAL_RECV_BUF_EMPTY && hiRet != HITLS_REC_NORMAL_IO_BUSY) {
        if (hiRet == HITLS_CM_LINK_CLOSED || hiRet == HITLS_REC_NORMAL_IO_EOF) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Connection was closed gracefully\n"));
            /* Connection closed by peer, notify upper layer and clean up */
            if (conn->recv) {
                conn->recv(conn->arg, conn, NULL, ERR_OK);
            }
            /* Close the connection to clean up resources */
            if (altcp_close(conn) != ERR_OK) {
                altcp_abort(conn);
            }
            return ERR_OK;
        } else {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_Read returned unexpected error: %d\n", hiRet));
            /* Unexpected error, close connection */
            if (conn->err) {
                conn->err(conn->arg, ERR_CLSD);
            }
            if (altcp_close(conn) != ERR_OK) {
                altcp_abort(conn);
            }
            return ERR_OK;
        }
    }
    
    return ERR_OK;
}

/**
 * Core function to process received data from lower layer
 */
static err_t altcp_hitls_lower_recv_process(struct altcp_pcb *conn, altcp_hitls_state_t *state)
{
    /* Check handshake status */
    uint8_t isDone = 0;
    int32_t hiRet = HITLS_IsHandShakeDone(state->hitls_ctx, &isDone);
    if (hiRet != HITLS_SUCCESS || !isDone) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Handshake in progress (ret=%d, done=%d)\n", hiRet, isDone));

        /* Perform handshake - call different functions based on whether it is a client or server */
        uint8_t isServer = 0;
        (void)HITLS_IsServer(state->hitls_ctx, &isServer);
        hiRet = isServer == 1 ? HITLS_Accept(state->hitls_ctx) : HITLS_Connect(state->hitls_ctx);
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Performing %s handshake\n", isServer ? "server" : "client"));

        /* Try to send handshake data */
        altcp_output(conn->inner_conn);

        /* Confirm that the bytes have been read */
        if (state->uio_bytes_read > 0) {
            altcp_recved(conn->inner_conn, state->uio_bytes_read);
            state->uio_bytes_read = 0;
        }

        if (hiRet == HITLS_REC_NORMAL_RECV_BUF_EMPTY || hiRet == HITLS_REC_NORMAL_IO_BUSY) {
            /* Handshake not completed, waiting for more data */
            LWIP_ASSERT("rx chain should be empty during handshake", state->rx == NULL);
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Handshake needs more data (ret=%d)\n", hiRet));
            return ERR_OK;
        }

        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS handshake failed: %d\n", hiRet));
            if (conn->err) {
                conn->err(conn->arg, ERR_CLSD);
            }
            if (altcp_close(conn) != ERR_OK) {
                altcp_abort(conn);
            }
            return ERR_OK;
        }

        /* Handshake successful */
        state->flags |= ALTCP_HITLS_FLAGS_HANDSHAKE_DONE;
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Handshake completed successfully\n"));

        /* Call connection success callback */
        if (conn->connected != NULL) {
            err_t err = conn->connected(conn->arg, conn, ERR_OK);
            if (err != ERR_OK) {
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Connected callback returned error: %d\n",
                    err));
                return err;
            }
        }
        if (state->rx == NULL) {
            return ERR_OK;
        }
    }

    /* Read application data - using HiTLS API */
    return altcp_hitls_handle_rx_appldata(conn, state);
}

/**
 * Lower layer receive callback function
 * Receive encrypted data from underlying TCP and pass it to HiTLS for processing
 */
static err_t altcp_hitls_lower_recv(void *arg, struct altcp_pcb *inner_conn, struct pbuf *p, err_t err)
{
    struct altcp_pcb *conn = (struct altcp_pcb *)arg;
    altcp_hitls_state_t *state;

    LWIP_UNUSED_ARG(inner_conn);

    if (conn == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_hitls_lower_recv: null connection\n"));
        if (p != NULL) {
            pbuf_free(p);
        }
        altcp_close(inner_conn);
        return ERR_CLSD;
    }

    state = (altcp_hitls_state_t *)conn->state;
    LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);
    if (state == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_hitls_lower_recv: null state\n"));
        if (p != NULL) {
            pbuf_free(p);
        }
        altcp_close(inner_conn);
        return ERR_CLSD;
    }

    /* handle NULL pbuf (inner connection closed) */
    if (p == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_hitls_lower_recv: connection close received\n"));
        /* remote host sent FIN, remember this (TLS state is destroyed when both sides are closed only!) */
        if ((state->flags & (ALTCP_HITLS_FLAGS_HANDSHAKE_DONE | ALTCP_HITLS_FLAGS_UPPER_CALLED)) ==
            (ALTCP_HITLS_FLAGS_HANDSHAKE_DONE | ALTCP_HITLS_FLAGS_UPPER_CALLED)) {
            /* need to notify upper layer (e.g. 'accept' called or 'connect' succeeded) */
            if ((state->rx != NULL) || (state->rx_app != NULL)) {
                state->flags |= ALTCP_HITLS_FLAGS_RX_CLOSE_QUEUED;
                /* this is a normal close (FIN) but we have unprocessed data, so delay the FIN */
                altcp_hitls_handle_rx_appldata(conn, state);
                return ERR_OK;
            }
            state->flags |= ALTCP_HITLS_FLAGS_RX_CLOSED;
            if (conn->recv) {
                return conn->recv(conn->arg, conn, NULL, ERR_OK);
            }
        } else {
            /* before connection setup is done: call 'err' */
            if (conn->err) {
                conn->err(conn->arg, ERR_ABRT);
            }
            altcp_close(conn);
        }
        return ERR_OK;
    }

    /* Handle receive error */
    if (err != ERR_OK) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_hitls_lower_recv: receive error: %d\n", err));
        pbuf_free(p);
        return altcp_close(conn);
    }

    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("altcp_hitls_lower_recv: received %d bytes\n", p->tot_len));

    /* Add received data to receive chain */
    if (state->rx == NULL) {
        state->rx = p;
    } else {
        LWIP_ASSERT("rx pbuf overflow", (int)p->tot_len + (int)p->len <= 0xFFFF);
        pbuf_cat(state->rx, p);
    }

    return altcp_hitls_lower_recv_process(conn, state);
}

/**
 * Send callback function - confirm that data has been sent
 */
static err_t altcp_hitls_lower_sent(void *arg, struct altcp_pcb *inner_conn, u16_t len)
{
    struct altcp_pcb *conn = (struct altcp_pcb *)arg;
    altcp_hitls_state_t *state;

    LWIP_UNUSED_ARG(inner_conn);

    if (conn == NULL) {
        return ERR_OK;
    }

    state = (altcp_hitls_state_t *)conn->state;
    LWIP_ASSERT("state", state != NULL);
    LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);

    /* HiTLS cannot get the number of un-sent bytes, so overhead cannot be updated */
    int32_t overhead = state->overhead_bytes_adjust > len ? len : state->overhead_bytes_adjust;
    /* Adjust overhead byte statistics */
    state->overhead_bytes_adjust -= len;
    uint16_t app_len = len - (uint16_t)overhead;

    if (app_len > 0) {
        state->overhead_bytes_adjust += app_len;
        /* Report the number of sent bytes to the upper layer */
        if (conn->sent != NULL) {
            return conn->sent(conn->arg, conn, app_len);
        }
    }
    return ERR_OK;
}

static void altcp_hitls_lower_err(void *arg, err_t err)
{
    struct altcp_pcb *conn = (struct altcp_pcb *)arg;

    if (conn != NULL && conn->err != NULL) {
        conn->err(conn->arg, err);
    }
}

/**
 * Set lower layer callback function
 */
static void altcp_hitls_setup_callbacks(struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
{
    altcp_arg(inner_conn, conn);
    altcp_recv(inner_conn, altcp_hitls_lower_recv);
    altcp_sent(inner_conn, altcp_hitls_lower_sent);
    altcp_err(inner_conn, altcp_hitls_lower_err);
    /* poll callback is set by application layer */
}

/**
 * Remove lower layer callback function
 */
static void altcp_hitls_remove_callbacks(struct altcp_pcb *inner_conn)
{
    altcp_arg(inner_conn, NULL);
    altcp_recv(inner_conn, NULL);
    altcp_sent(inner_conn, NULL);
    altcp_err(inner_conn, NULL);
    altcp_poll(inner_conn, NULL, inner_conn->pollinterval);
}

/**
 * Allocate HiTLS state structure
 */
static altcp_hitls_state_t *altcp_hitls_alloc(void *conf)
{
    altcp_hitls_state_t *state = (altcp_hitls_state_t *)mem_malloc(sizeof(altcp_hitls_state_t));
    if (state) {
        (void)memset_s(state, sizeof(altcp_hitls_state_t), 0, sizeof(altcp_hitls_state_t));
        state->conf = conf;
    }
    return state;
}

/**
 * Release HiTLS state structure
 */
static void altcp_hitls_free(void *conf, altcp_hitls_state_t *state)
{
    LWIP_UNUSED_ARG(conf);

    if (state) {
        mem_free(state);
    }
}

/**
 * Set connection - core initialization function
 */
static err_t altcp_hitls_setup(void *conf, struct altcp_pcb *conn, struct altcp_pcb *inner_conn)
{
    int32_t hiRet;
    struct altcp_tls_config *config = (struct altcp_tls_config *)conf;
    altcp_hitls_state_t *state;

    if (!conf) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null configuration\n"));
        return ERR_ARG;
    }
    LWIP_ASSERT("invalid inner_conn", conn != inner_conn);

    /* Allocate HiTLS state */
    state = altcp_hitls_alloc(conf);
    if (!state) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to allocate state\n"));
        return ERR_MEM;
    }

    /* Create HiTLS context - key difference point */
    state->hitls_ctx = HITLS_New(config->hitls_config);
    if (!state->hitls_ctx) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_New failed\n"));
        altcp_hitls_free(conf, state);
        return ERR_MEM;
    }

    /* Create and set UIO - different from mbedtls's bio setting */
    state->uio = altcp_hitls_create_uio(conn, state);
    if (!state->uio) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Create UIO failed\n"));
        HITLS_Free(state->hitls_ctx);
        altcp_hitls_free(conf, state);
        return ERR_MEM;
    }

    /* Bind UIO to HiTLS context */
    hiRet = HITLS_SetUio(state->hitls_ctx, state->uio);
    if (hiRet != HITLS_SUCCESS) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_SetUio failed: %d\n", hiRet));
        BSL_UIO_Free(state->uio);
        HITLS_Free(state->hitls_ctx);
        altcp_hitls_free(conf, state);
        return ERR_MEM;
    }

    /* Set connection role (client/server) */
    hiRet = HITLS_SetEndPoint(state->hitls_ctx, !config->is_server);
    if (hiRet != HITLS_SUCCESS) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_SetEndPoint failed: %d\n", hiRet));
        BSL_UIO_Free(state->uio);
        HITLS_Free(state->hitls_ctx);
        altcp_hitls_free(conf, state);
        return ERR_MEM;
    }

    /* Set ALTCP callback and state */
    altcp_hitls_setup_callbacks(conn, inner_conn);
    conn->inner_conn = inner_conn;
    conn->fns = &altcp_hitls_functions;
    conn->state = state;

    return ERR_OK;
}

/**
 * Write data function - using HiTLS to encrypt data
 */
static err_t altcp_hitls_write(struct altcp_pcb *conn, const void *dataptr, u16_t len, uint8_t apiflags)
{
    int32_t ret;
    altcp_hitls_state_t *state;
    uint32_t writeLen = 0;

    LWIP_UNUSED_ARG(apiflags);

    if (conn == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null connection\n"));
        return ERR_VAL;
    }

    state = (altcp_hitls_state_t *)conn->state;
    if (state == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null state\n"));
        return ERR_ARG;
    }

    /* Check if handshake is completed */
    if (!(state->flags & ALTCP_HITLS_FLAGS_HANDSHAKE_DONE)) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Handshake not completed yet\n"));
        return ERR_VAL;
    }

    /* Use HiTLS to write data - key difference point */
    ret = HITLS_Write(state->hitls_ctx, (const uint8_t *)dataptr, len, &writeLen);

    /* Try to send data */
    altcp_output(conn->inner_conn);

    if (ret == HITLS_SUCCESS && writeLen == len) {
        /* Update application send counter */
        state->overhead_bytes_adjust -= len;
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Successfully wrote %d bytes\n", len));
        return ERR_OK;
    } else if (ret == HITLS_REC_NORMAL_IO_BUSY) {
        /* HiTLS version's WANT_WRITE equivalent state */
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Operation would block, wrote %d/%d bytes\n", writeLen, len));
        return ERR_MEM;
    } else {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_Write failed: %d, wrote %d/%d bytes\n", ret, writeLen, len));
        return ERR_VAL;
    }
}

/**
 * Get maximum transmission unit
 */
static u16_t altcp_hitls_mss(struct altcp_pcb *conn)
{
    if (conn == NULL) {
        return 0;
    }

    /* Get underlying MSS, reserve TLS overhead */
    u16_t inner_mss = altcp_mss(conn->inner_conn);
    if (inner_mss > 64) {
        return inner_mss - 64; /* Reserve TLS record header and MAC overhead */
    }
    return inner_mss;
}

/**
 * Release connection resources
 */
static void altcp_hitls_dealloc(struct altcp_pcb *conn)
{
    if (conn == NULL) {
        return;
    }

    altcp_hitls_state_t *state = (altcp_hitls_state_t *)conn->state;
    if (state != NULL) {
        /* Release HiTLS context */
        if (state->hitls_ctx != NULL) {
            HITLS_Free(state->hitls_ctx);
        }

        /* Release UIO and its context */
        if (state->uio != NULL) {
            altcp_hitls_uio_ctx_t *uio_ctx = (altcp_hitls_uio_ctx_t *)BSL_UIO_GetUserData(state->uio);
            if (uio_ctx != NULL) {
                mem_free(uio_ctx);
            }
            BSL_UIO_Free(state->uio);
        }

        /* Release pending pbuf */
        if (state->rx != NULL) {
            pbuf_free(state->rx);
        }
        if (state->rx_app != NULL) {
            pbuf_free(state->rx_app);
        }

        /* Clear flags and release state */
        state->flags = 0;
        altcp_hitls_free(state->conf, state);
        conn->state = NULL;
    }
}

/**
 * Close TLS connection
 */
static err_t altcp_hitls_close(struct altcp_pcb *conn)
{
    struct altcp_pcb *inner_conn;
    if (conn == NULL) {
        return ERR_VAL;
    }

    inner_conn = conn->inner_conn;
    if (inner_conn != NULL) {
        err_t err;
        altcp_poll_fn oldpoll = inner_conn->poll;
        altcp_hitls_remove_callbacks(inner_conn);
        err = altcp_close(inner_conn);
        if (err != ERR_OK) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG | LWIP_DBG_LEVEL_WARNING, ("Inner connection close failed: %d\n", err));
            /* Close failed, restore all callbacks */
            altcp_hitls_setup_callbacks(conn, inner_conn);
            /* poll callback is not included in setup_callbacks, need to restore separately */
            altcp_poll(inner_conn, oldpoll, inner_conn->pollinterval);
            return err;
        }

        conn->inner_conn = NULL;
    }

    /* Release TLS connection itself */
    altcp_free(conn);
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HiTLS connection closed successfully\n"));
    return ERR_OK;
}

/**
 * TCP connection establishment callback - start TLS handshake after TCP connection is established
 */
static err_t altcp_hitls_lower_connected(void *arg, struct altcp_pcb *inner_conn, err_t err)
{
    struct altcp_pcb *conn = (struct altcp_pcb *)arg;
    LWIP_UNUSED_ARG(inner_conn);
    if (conn != NULL && conn->state != NULL) {
        altcp_hitls_state_t *state = (altcp_hitls_state_t *)conn->state;
        LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);

        /* If TCP connection fails, directly call upper layer connected callback */
        if (err != ERR_OK) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("TCP connection failed: %d\n", err));
            if (conn->connected) {
                return conn->connected(conn->arg, conn, err);
            }
        }

        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("TCP connection established, starting TLS handshake\n"));

        /* Ensure overhead statistics are valid */
        state->overhead_bytes_adjust = 0;

        /* Start TLS handshake - for client connections, this will immediately start the handshake process */
        return altcp_hitls_lower_recv_process(conn, state);
    }

    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Invalid connection state\n"));
    return ERR_VAL;
}

static err_t altcp_hitls_connect(struct altcp_pcb *conn, const ip_addr_t *ipaddr, u16_t port,
    altcp_connected_fn connected)
{
    if (conn == NULL) {
        return ERR_VAL;
    }
    conn->connected = connected;
    return altcp_connect(conn->inner_conn, ipaddr, port, altcp_hitls_lower_connected);
}

/** Accept callback from lower connection (i.e. TCP)
 * Allocate one of our structures, assign it to the new connection's 'state' and
 * call the new connection's 'accepted' callback. If that succeeds, we wait
 * to receive connection setup handshake bytes from the client.
 */
static err_t altcp_hitls_lower_accept(void *arg, struct altcp_pcb *accepted_conn, err_t err)
{
    struct altcp_pcb *listen_conn = (struct altcp_pcb *)arg;
    if (listen_conn && listen_conn->state && listen_conn->accept) {
        err_t setup_err;
        altcp_hitls_state_t *listen_state = (altcp_hitls_state_t *)listen_conn->state;
        /* create a new altcp_conn to pass to the next 'accept' callback */
        struct altcp_pcb *new_conn = altcp_alloc();
        if (new_conn == NULL) {
            return ERR_MEM;
        }
        setup_err = altcp_hitls_setup(listen_state->conf, new_conn, accepted_conn);
        if (setup_err != ERR_OK) {
            altcp_free(new_conn);
            return setup_err;
        }
        return listen_conn->accept(listen_conn->arg, new_conn, err);
    }
    return ERR_ARG;
}

static struct altcp_pcb *altcp_hitls_listen(struct altcp_pcb *conn, u8_t backlog, err_t *err)
{
    if (conn == NULL) {
        return NULL;
    }
    struct altcp_pcb *lpcb = altcp_listen_with_backlog_and_err(conn->inner_conn, backlog, err);
    if (lpcb == NULL) {
        return NULL;
    }
    altcp_hitls_state_t *state = (altcp_hitls_state_t *)conn->state;
    /* Free members of the ssl context (not used on listening pcb). This
       includes freeing input/output buffers, so saves ~32KByte by default */
    HITLS_Free(state->hitls_ctx);
    state->hitls_ctx = NULL;

    conn->inner_conn = lpcb;
    altcp_accept(lpcb, altcp_hitls_lower_accept);
    return conn;
}

static void altcp_hitls_abort(struct altcp_pcb *conn)
{
    if (conn != NULL) {
        altcp_abort(conn->inner_conn);
    }
}

static void altcp_hitls_recved(struct altcp_pcb *conn, u16_t len)
{
    if (conn == NULL) {
        return;
    }
    altcp_hitls_state_t *state = (altcp_hitls_state_t *)conn->state;
    if (state == NULL) {
        return;
    }
    if (!(state->flags & ALTCP_HITLS_FLAGS_HANDSHAKE_DONE)) {
        return;
    }
    u16_t lower_recved = len;
    if (lower_recved > state->rx_passed_unrecved) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG, ("bogus recved count (len > state->rx_passed_unrecved / %d / %d)\n",
                                      len, state->rx_passed_unrecved));
        lower_recved = (u16_t)state->rx_passed_unrecved;
    }
    state->rx_passed_unrecved -= lower_recved;
    altcp_recved(conn->inner_conn, lower_recved);
}

/**
 * Poll callback from lower connection (i.e. TCP)
 * Just pass this on to the application and handle any pending data
 */
static err_t altcp_hitls_lower_poll(void *arg, struct altcp_pcb *inner_conn)
{
    struct altcp_pcb *conn = (struct altcp_pcb *)arg;
    LWIP_UNUSED_ARG(inner_conn);

    if (conn == NULL) {
        return ERR_OK;
    }
    LWIP_ASSERT("pcb mismatch", conn->inner_conn == inner_conn);

    /* Check if there's unreceived rx data */
    if (conn->state) {
        altcp_hitls_state_t *state = (altcp_hitls_state_t *)conn->state;
        /* Try to process any pending data */
        if (altcp_hitls_handle_rx_appldata(conn, state) == ERR_ABRT) {
            return ERR_ABRT;
        }
    }
    
    /* Pass poll to upper layer */
    if (conn->poll) {
        return conn->poll(conn->arg, conn);
    }
    
    return ERR_OK;
}

/**
 * Set poll callback for the connection
 */
static void altcp_hitls_set_poll(struct altcp_pcb *conn, u8_t interval)
{
    if (conn != NULL) {
        altcp_poll(conn->inner_conn, altcp_hitls_lower_poll, interval);
    }
}

/**
 * ALTCP function table
 */
static const struct altcp_functions altcp_hitls_functions = {
    .set_poll = altcp_hitls_set_poll,
    .recved = altcp_hitls_recved,
    .bind = altcp_default_bind,
    .connect = altcp_hitls_connect,
    .listen = altcp_hitls_listen,
    .abort = altcp_hitls_abort,
    .close = altcp_hitls_close,
    .shutdown = altcp_default_shutdown,
    .write = altcp_hitls_write,
    .output = altcp_default_output,
    .mss = altcp_hitls_mss,
    .sndbuf = altcp_default_sndbuf,
    .sndqueuelen = altcp_default_sndqueuelen,
    .nagle_disable = altcp_default_nagle_disable,
    .nagle_enable = altcp_default_nagle_enable,
    .nagle_disabled = altcp_default_nagle_disabled,
    .setprio = altcp_default_setprio,
    .dealloc = altcp_hitls_dealloc,
    .addrinfo = altcp_default_get_tcp_addrinfo,
    .getip = altcp_default_get_ip,
    .getport = altcp_default_get_port
#if LWIP_TCP_KEEPALIVE
    , .keepalive_disable = altcp_default_keepalive_disable,
    .keepalive_enable = altcp_default_keepalive_enable
#endif
#ifdef LWIP_DEBUG
    , .dbg_get_tcp_state = altcp_default_dbg_get_tcp_state
#endif
};

/* ========== Public API implementation ========== */

/**
 * Wrap existing PCB as TLS connection
 */
struct altcp_pcb *altcp_tls_wrap(struct altcp_tls_config *config, struct altcp_pcb *inner_pcb)
{
    struct altcp_pcb *ret;

    if (inner_pcb == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null inner PCB\n"));
        return NULL;
    }

    if (config == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null configuration\n"));
        return NULL;
    }

    ret = altcp_alloc();
    if (ret != NULL) {
        if (altcp_hitls_setup(config, ret, inner_pcb) != ERR_OK) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Setup failed\n"));
            altcp_free(ret);
            return NULL;
        }
    } else {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to allocate PCB\n"));
    }

    return ret;
}

/**
 * Create new TLS connection
 */
struct altcp_pcb *altcp_tls_new(struct altcp_tls_config *config, uint8_t ip_type)
{
    struct altcp_pcb *inner_pcb, *ret;

    if (config == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Null configuration\n"));
        return NULL;
    }

    inner_pcb = altcp_tcp_new_ip_type(ip_type);
    if (inner_pcb == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to create inner TCP PCB\n"));
        return NULL;
    }

    ret = altcp_tls_wrap(config, inner_pcb);
    if (ret == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to wrap TCP PCB\n"));
        altcp_close(inner_pcb);
    }
    return ret;
}

/**
 * Allocator function, used for altcp_new
 */
struct altcp_pcb *altcp_tls_alloc(void *arg, uint8_t ip_type)
{
    return altcp_tls_new((struct altcp_tls_config *)arg, ip_type);
}

void altcp_tls_free_config(struct altcp_tls_config *conf)
{
    if (conf != NULL) {
        if (conf->hitls_config) {
            HITLS_CFG_FreeConfig(conf->hitls_config);
        }
        if (conf->userdata != NULL) {
            if (conf->userdata->pass != NULL) {
                (void)memset_s(conf->userdata->pass, conf->userdata->len, 0, conf->userdata->len);
                mem_free(conf->userdata->pass);
                conf->userdata->pass = NULL;
            }
            mem_free(conf->userdata);
            conf->userdata = NULL;
        }
        mem_free(conf);
    }
}

static int32_t hitls_password_cb(char *buf, int32_t bufLen, int32_t flag, void *userdata)
{
    (void)flag;
    altcp_hilts_pass_userdata_t *data = (altcp_hilts_pass_userdata_t *)userdata;
    if (data == NULL || data->pass == NULL || data->len == 0) {
        return 0;
    }
    if (memcpy_s(buf, bufLen, data->pass, data->len) != 0) {
        return 0;
    }
    return data->len;
}

static altcp_hilts_pass_userdata_t *create_password_cb_userdata(const uint8_t *privkey_pass, size_t privkey_pass_len)
{
    altcp_hilts_pass_userdata_t *userdata = (altcp_hilts_pass_userdata_t *)mem_malloc(
        sizeof(altcp_hilts_pass_userdata_t));
    if (userdata == NULL) {
        return NULL;
    }
    uint8_t *pass = (uint8_t *)mem_malloc(privkey_pass_len);
    if (pass == NULL) {
        mem_free(userdata);
        return NULL;
    }
    if (memcpy_s(pass, privkey_pass_len, privkey_pass, privkey_pass_len) != 0) {
        mem_free(userdata);
        mem_free(pass);
        return NULL;
    }
    userdata->pass = pass;
    userdata->len = privkey_pass_len;
    return userdata;
}

/* Create a new TLS configuration */
static struct altcp_tls_config *altcp_hitls_create_config(int32_t is_server,
    const uint8_t *privkey, size_t privkey_len, const uint8_t *privkey_pass, size_t privkey_pass_len,
    const uint8_t *cert, size_t cert_len
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
    , uint8_t isAuth
#endif
)
{
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Creating %s configuration\n", is_server ? "server" : "client"));

    /* Allocate configuration structure */
    struct altcp_tls_config *config = (struct altcp_tls_config *)mem_malloc(sizeof(struct altcp_tls_config));
    if (config == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Failed to allocate config\n"));
        return NULL;
    }
    (void)memset_s(config, sizeof(struct altcp_tls_config), 0, sizeof(struct altcp_tls_config));
    config->is_server = is_server;

    /* Create HiTLS configuration - server */
#ifdef LWIP_ALTCP_HITLS_PROVIDER
    config->hitls_config = HITLS_CFG_ProviderNewTLSConfig(g_libCtx, g_attr);
#else
    config->hitls_config = HITLS_CFG_NewTLSConfig();
#endif
    if (config->hitls_config == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("HITLS_CFG_NewTLSConfig failed\n"));
        mem_free(config);
        return NULL;
    }

    (void)HITLS_CFG_SetVersion(config->hitls_config, HITLS_VERSION_TLS12, HITLS_VERSION_TLS13);
    altcp_hitls_configure_auth_mode(config->hitls_config, isAuth);

    int32_t hiRet;
    /* Set terminal certificate */
    if (cert != NULL && cert_len > 0) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Loading certificate (%ld bytes)\n", cert_len));
        hiRet = HITLS_CFG_LoadCertBuffer(config->hitls_config, cert, cert_len, TLS_PARSE_FORMAT_PEM);
        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Load certificate failed: %d\n", hiRet));
            goto ERR;
        }
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Certificate loaded successfully\n"));
    }

    if (privkey != NULL && privkey_len > 0) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Loading private key (%ld bytes)\n", privkey_len));
        if (privkey_pass != NULL && privkey_pass_len > 0) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Setting up password callback\n"));
            /* Set private key */
            altcp_hilts_pass_userdata_t *userdata = create_password_cb_userdata(privkey_pass, privkey_pass_len);
            if (userdata == NULL) {
                goto ERR;
            }
            config->userdata = userdata;
            hiRet = HITLS_CFG_SetDefaultPasswordCb(config->hitls_config, hitls_password_cb);
            if (hiRet != HITLS_SUCCESS) {
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Set password callback failed: %d\n", hiRet));
                goto ERR;
            }
            hiRet = HITLS_CFG_SetDefaultPasswordCbUserdata(config->hitls_config, userdata);
            if (hiRet != HITLS_SUCCESS) {
                LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Set password callback userdata failed: %d\n", hiRet));
                goto ERR;
            }
        }
        hiRet = HITLS_CFG_LoadKeyBuffer(config->hitls_config, privkey, privkey_len, TLS_PARSE_FORMAT_PEM);
        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Load private key failed: %d\n", hiRet));
            goto ERR;
        }
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Private key loaded successfully\n"));
    }
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Configuration created successfully\n"));
    return config;
ERR:
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Configuration creation failed\n"));
    altcp_tls_free_config(config);
    return NULL;
}

/**
 * Create server configuration (single certificate)
 */
struct altcp_tls_config *altcp_tls_create_config_server_privkey_cert(const uint8_t *privkey, size_t privkey_len,
                            const uint8_t *privkey_pass, size_t privkey_pass_len,
                            const uint8_t *cert, size_t cert_len)
{
    return altcp_hitls_create_config(1, privkey, privkey_len, privkey_pass, privkey_pass_len,
        cert, cert_len
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
        , 0
#endif
);
}

/**
 * Create client configuration
 */
struct altcp_tls_config *altcp_tls_create_config_client(const uint8_t *ca, size_t ca_len
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
    , uint8_t isAuth
#endif
)
{
    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Creating client configuration\n"));

    struct altcp_tls_config *conf = altcp_hitls_create_config(0, NULL, 0, NULL, 0, NULL, 0
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
    , isAuth
#endif
    );
    if (conf == NULL) {
        return NULL;
    }

    if (ca != NULL && ca_len > 0) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Loading CA certificate bundle (%ld bytes)\n", ca_len));
        int32_t hiRet = add_ca_certs_to_config(conf->hitls_config, ca, ca_len);
        if (hiRet != HITLS_SUCCESS) {
            altcp_tls_free_config(conf);
            return NULL;
        }
    }

    LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Client configuration created successfully\n"));
    return conf;
}

static int32_t add_crl_to_config(struct altcp_tls_config *conf, const uint8_t *crl, size_t crl_len)
{
    if (crl == NULL || crl_len == 0) {
        return HITLS_SUCCESS;
    }
    /* Get tls store from conf */
    HITLS_CERT_Store *store = HITLS_CFG_GetCertStore(conf->hitls_config);
    if (store == NULL) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Get tls store failed\n"));
        return HITLS_INVALID_INPUT;
    }
    /* Add crl to store */
    BSL_Buffer crlBuf = {.data = (uint8_t *)(uintptr_t)crl, .dataLen = crl_len};
    HITLS_X509_List *crlList = NULL;
    int32_t hiRet = HITLS_X509_CrlParseBundleBuff(BSL_FORMAT_PEM, &crlBuf, &crlList);
    if (hiRet != HITLS_SUCCESS) {
        LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("CRL parse failed: %d\n", hiRet));
        return hiRet;
    }
    HITLS_X509_Crl *hiCrl = BSL_LIST_GET_FIRST(crlList);
    while (hiCrl != NULL) {
        hiRet = HITLS_X509_StoreCtxCtrl(store, HITLS_X509_STORECTX_SET_CRL, hiCrl, 0);
        if (hiRet != HITLS_SUCCESS) {
            LWIP_DEBUGF(ALTCP_HITLS_DEBUG_SERIOUS, ("Add CRL to store failed: %d\n", hiRet));
            BSL_LIST_FREE(crlList, (BSL_LIST_PFUNC_FREE)HITLS_X509_CrlFree);
            return hiRet;
        }
        hiCrl = BSL_LIST_GET_NEXT(crlList);
    }
    /* Free the CRL list after all CRLs have been added to the store */
    BSL_LIST_FREE(crlList, (BSL_LIST_PFUNC_FREE)HITLS_X509_CrlFree);
    return HITLS_SUCCESS;
}

/**
 * Create two-way authentication client configuration
 */
struct altcp_tls_config *altcp_tls_create_config_client_2wayauth(const uint8_t *ca, size_t ca_len,
    const uint8_t *privkey, size_t privkey_len, const uint8_t *privkey_pass, size_t privkey_pass_len,
    const uint8_t *cert, size_t cert_len
#ifdef LWIP_ALTCP_HITLS_CRL
    , const uint8_t *crl, size_t crl_len
#endif
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
    , uint8_t isAuth
#endif
)
{
    struct altcp_tls_config *conf = altcp_hitls_create_config(0, privkey, privkey_len, privkey_pass, privkey_pass_len,
        cert, cert_len
#ifdef LWIP_ALTCP_HITLS_AUTH_MODE
        , isAuth
#endif
    );
    if (conf == NULL) {
        return NULL;
    }
    if (ca != NULL && ca_len > 0) {
        int32_t hiRet = add_ca_certs_to_config(conf->hitls_config, ca, ca_len);
        if (hiRet != HITLS_SUCCESS) {
            altcp_tls_free_config(conf);
            return NULL;
        }
    }

#ifdef LWIP_ALTCP_HITLS_CRL
    if (add_crl_to_config(conf, crl, crl_len) != HITLS_SUCCESS) {
        altcp_tls_free_config(conf);
        return NULL;
    }
#endif
    return conf;
}

#endif /* LWIP_ALTCP & LWIP_ALTCP_TLS_HITLS */
